/*
 * Copyright 2008 Nils Adermann
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <iostream>
#include <sstream>
#include <cstring>
#include <curl/curl.h>

#include "couchdb++/Session.h"
#include "couchdb++/CurlException.h"
#include "couchdb++/Database.h"

using namespace Couch;

Session::Session(const std::string& host, int port) :
	host(host), port(port), ssl(false), useAuth(false)
{
	init();
}

Session::Session(const std::string& host, bool ssl, int port) :
	host(host), port(port), ssl(ssl), useAuth(false)
{
	init();
}

Session::Session(const std::string& host, const std::string& username, const std::string& password, int port) :
	host(host), port(port), ssl(false), useAuth(true), username(username), password(password)
{
	init();
}

Session::Session(const std::string& host, const std::string& username, const std::string& password, bool ssl, int port) :
	host(host), port(port), ssl(ssl), useAuth(true), username(username), password(password)
{
	init();
}

Session::~Session()
{
	if (easyhandle)
	{
		curl_easy_cleanup(easyhandle);
	}

	if (curlMessage)
	{
		delete[] curlMessage;
	}
}

void Session::init()
{
	std::stringstream ss;
	std::string portString;

	baseUrl.append((ssl) ? "https://" : "http://");
	baseUrl.append(host);
	baseUrl.append(":");
	ss << port;
	ss >> portString;
	baseUrl.append(portString);

	initEasyhandle();
}

void Session::initEasyhandle()
{
	easyhandle = curl_easy_init();

	curlMessage = new char[CURL_ERROR_SIZE];

	// set the callback to a static method but pass this object to it so it always knows
	// what object the data belongs to
	curl_easy_setopt(easyhandle, CURLOPT_WRITEFUNCTION, Session::staticWriteDataCallback);
	curl_easy_setopt(easyhandle, CURLOPT_WRITEDATA, this);
	curl_easy_setopt(easyhandle, CURLOPT_ERRORBUFFER, curlMessage);

	if (useAuth)
	{
		std::string authString(username);
		authString.append(":");
		authString.append(password);
		curl_easy_setopt(easyhandle, CURLOPT_USERPWD, authString.c_str());
	}
}

void Session::setVerbose(bool verbose)
{
	curl_easy_setopt(easyhandle, CURLOPT_VERBOSE, verbose);
	this->verbose = verbose;
}

Database* Session::getDatabase(const std::string& name)
{
	JSON::Object* db = &(get(name)->toObject());

	return new Database(db, this);
}

std::string Session::buildUrl(const std::string& relativeUrl)
{
	std::string url = baseUrl;

	// add a slash if necessary
	if (relativeUrl.length() == 0 || relativeUrl[0] != '/')
	{
		url.append("/");
	}
	url.append(relativeUrl);

	return url;
}

JSON::Value* Session::get(const std::string& relativeUrl)
{
	std::string url = buildUrl(relativeUrl);

	jsonParser.reset();

	curl_easy_setopt(easyhandle, CURLOPT_URL, url.c_str());
	CURLcode error = curl_easy_perform(easyhandle);

	if (error)
	{
		UnicodeString message("Performing GET request on ");
		message.append(url.c_str());
		throw CurlException(message, error, curlMessage); 
	}

	return jsonParser.finish();
}

size_t Session::staticWriteDataCallback(void *ptr, size_t size, size_t nmemb, void* session)
{
	return static_cast<Session*>(session)->writeDataCallback(ptr, size, nmemb);
}

size_t Session::writeDataCallback(void* ptr, size_t size, size_t nmemb)
{
	if (verbose)
	{
		char* tmpStr = new char[size*nmemb + 1];
		strncpy(tmpStr, (const char*)ptr, size*nmemb);
		tmpStr[size*nmemb] = '\0';
		std::cout << "read chunk (size=" << size << ", nmemb=" << nmemb << ", length=" << size*nmemb << "): \n" << tmpStr << "\n\n";
	}

	jsonParser.read((const char*) ptr, size*nmemb);

	return size * nmemb;
}

